refactor: modularize event loop node class methods and helpers #6633
Conversation
Extract EventLoopNode helper logic into focused event_loop modules while keeping the node responsible for orchestration. Preserve the existing behavior and compatibility for compaction, event publishing, cursor persistence, synthetic tools, judge evaluation, stall detection, tool result handling, and subagent escalation wiring.
Apply Ruff formatting to the extracted event loop modules, the EventLoopNode wrappers, and the OpenRouter key check script so the lint CI format check passes cleanly.
|
please review it and let me know if any changes needed |
|
hey @TimothyZhang7 please review this PR (assigned by yourself) |
TimothyZhang7
left a comment
There was a problem hiding this comment.
Review
The decomposition direction is right — event_loop_node.py was genuinely too large, and the 8 submodule boundaries (compaction, cursor_persistence, event_publishing, judge_pipeline, stall_detector, subagent_executor, synthetic_tools, tool_result_handler) are cohesive. Backward compatibility is preserved. But there are structural issues that should be addressed before merge.
Circular import not resolved
judge_pipeline.py imports back from the parent it was extracted from:
# judge_pipeline.py
from framework.graph.event_loop_node import JudgeVerdictJudgeVerdict should be moved to a shared types file (e.g. framework/graph/event_loop/types.py) so submodules don't depend on their parent.
Any used as a circular-import workaround
subagent_executor.py, compaction.py, and judge_pipeline.py type key parameters as Any with comments like # NodeContext, # LoopConfig, # OutputAccumulator. event_publishing.py shows this is avoidable — it imports EventBus and NodeContext directly. The others should use TYPE_CHECKING guards to get real types without runtime circular imports.
Callback injection as circular-import workaround
compaction.compact() takes build_message_inventory_fn, publish_context_usage_fn, and write_debug_log_fn as optional Callable parameters. This is dependency injection to dodge a circular import — better to fix the import structure so the function can call them directly.
Local imports inside delegation methods
def _build_ask_user_tool(self) -> Tool:
from framework.graph.event_loop.synthetic_tools import build_ask_user_tool
return build_ask_user_tool()Method-level imports work but are a symptom of the same unresolved circular import architecture.
Minor
- Em dash changed to hyphen in a user-visible return string (
— description:→- description:) — a semantic change embedded in a refactor. core/frontend/package-lock.jsonappears to be a spurious change unrelated to this refactor.- Several meaningful inline comments and docstrings were deleted from
event_loop_node.pythat documented non-obvious behavior.
The core issue across items 1–4 is the same: the import graph between event_loop_node.py and its submodules wasn't fully resolved, so the circular dependency was patched over rather than fixed. Resolving the JudgeVerdict circular dep and using TYPE_CHECKING imports would address most of it.
|
thanks for the review , I'm looking into these things |
I have addressed most of the issues . please review it and let me know if any further changes needed cc: @TimothyZhang7 , thanks |
levxn
left a comment
There was a problem hiding this comment.
Small fixes, address them, thanks!
| @@ -81,28 +139,14 @@ async def _describe_images_as_text(image_content: list[dict[str, Any]]) -> str | | |||
| if description: | |||
| count = len(image_content) | |||
| label = "image" if count == 1 else f"{count} images" | |||
| return f"[{label} attached — description: {description}]" | |||
| return f"[{label} attached \u2014 description: {description}]" | |||
There was a problem hiding this comment.
Please revert to keep this a clean refactor with no unintended diffs.
| @@ -81,28 +139,14 @@ async def _describe_images_as_text(image_content: list[dict[str, Any]]) -> str | | |||
| if description: | |||
| count = len(image_content) | |||
| label = "image" if count == 1 else f"{count} images" | |||
| return f"[{label} attached — description: {description}]" | |||
| return f"[{label} attached \u2014 description: {description}]" | |||
There was a problem hiding this comment.
| return f"[{label} attached \u2014 description: {description}]" | |
| return f"[{label} attached — description: {description}]" |
There was a problem hiding this comment.
These "peer": true additions are unrelated to this backend refactor.
| char_limit=self._LLM_COMPACT_CHAR_LIMIT, | ||
| max_depth=self._LLM_COMPACT_MAX_DEPTH, | ||
| max_context_tokens=self._config.max_context_tokens, | ||
| ) | ||
|
|
||
| async def _llm_compact_split( |
There was a problem hiding this comment.
this function is a deadcode, can be removed, and also its relevant import in line 28.
There was a problem hiding this comment.
__llm_compact_split is used in compaction.py line number 182
for summarising the messages when context limit hits
There was a problem hiding this comment.
remove the dead _llm_compact_split import and delegation wrapper in ore/framework/graph/event_loop_node.py. The one you are using in compaction.py has been declared separately inside its respective file, check properly.
done cc: @levxn |
1eac28d to
9667dd2
Compare
levxn
left a comment
There was a problem hiding this comment.
Please rebase off main cleanly and ensure only the EventLoopNode modularization changes are in this PR. As-is, this cannot be reviewed or merged safely.
done , please look now |
levxn
left a comment
There was a problem hiding this comment.
Good to merge, @TimothyZhang7 you can confirm from your end as well
Description
Modularizes
EventLoopNodeby extracting helper responsibilities into focusedevent_loopmodules while preserving existing behavior and public compatibility. The orchestration flow remains inevent_loop_node.py, and the extracted logic is wired back through thin delegation methods.Type of Change
Related Issues
Fixes #6596
Changes Made
EventLoopNodeto delegate to the extracted modules while keeping the main orchestration logic in place.Testing
Describe the tests you ran to verify your changes:
cd core && pytest tests/)cd core && ruff check .)Targeted verification performed:
tests/test_event_loop_node.pytests/test_event_loop_wiring.pytests/test_node_conversation.py::TestIsContextTooLargeErrortests/test_node_conversation.py::TestLlmCompacttests/test_subagent.py -k "EscalationReceiver or SubagentJudge or wait_for_response_registers_receiver_in_registry"tests/test_subagent_escalation_e2e.py::test_escalation_cleanup_after_completionruff check framework/graph/event_loop_node.py framework/graph/event_loopChecklist
Screenshots (if applicable)
Not applicable.